package editorsvg.editor.atributos;

import editorsvg.utilidades.Angulo;
import editorsvg.utilidades.Flotantes;
import editorsvg.utilidades.Formato;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.apache.batik.parser.AWTTransformProducer;
import org.apache.batik.parser.TransformListParser;

/**
 * Transformación que se aplica a los elementos graficos.
 * @author Jorge Berjano
 */
public class AtrTransformacion extends Atributo {

    private AffineTransform affineTransform = new AffineTransform();

    public AtrTransformacion() {
    }
        
    @Override
    public Object clone() throws CloneNotSupportedException {
        AtrTransformacion clon = (AtrTransformacion) super.clone();
        clon.affineTransform = (AffineTransform) affineTransform.clone();
        return clon;
    }

    public AffineTransform getAffineTransform() {
        return affineTransform;
    }

    public void combinar(AffineTransform affineTransform) {
        this.affineTransform.preConcatenate(affineTransform);
        generarTexto();
    }

//    public AffineTransform crearInvertida() {
//        AffineTransform inv;
//        try {
//            inv = affineTransform.createInverse();
//        } catch (NoninvertibleTransformException ex) {
//            return null;
//        }
//        return inv;
//    }

    public void escalar(double sx, double sy) {
        affineTransform.scale(sx, sy);
        generarTexto();
    }               

    public void limpiar() {
        affineTransform.setToIdentity();
        generarTexto();
    }

    /**
     * Convierte un punto transformado en el punto original
     * @param p1 El punto transformado que se conviente en original.
     * @return El punto original
     */
    private Point2D ajustar(Point2D p1) {
        Point2D p0 = new Point2D.Double(0, 0);

        AffineTransform inv;
        try {
            inv = affineTransform.createInverse();
        } catch (NoninvertibleTransformException ex) {
            System.err.println("La matriz de transformacion no es invertible");
            return null;
        }

        Point2D pt0 = inv.transform(p0, null);
        Point2D pt1 = inv.transform(p1, null);

        double x = pt1.getX() - pt0.getX();
        double y = pt1.getY() - pt0.getY();
        p1.setLocation(x, y);
        return p1;
    }

    public void desplazar(double dx, double dy) {

        Point2D p = ajustar(new Point2D.Double(dx, dy));
        dx = p.getX();
        dy = p.getY();
        affineTransform.translate(dx, dy);
        generarTexto();
    }

    /**
     * Agrega a la transformación un escalado para que un encuadre inicial se 
     * transforme en un encuadre final.
     * @param encuadreInicial Encuadre inicial
     * @param encuadreFinal Encuadre final
     */
    public void escalar(Rectangle2D encuadreInicial, Rectangle2D encuadreFinal) {

        Point2D p0 = ajustar(new Point2D.Double(encuadreInicial.getX(), encuadreInicial.getY()));
        if (p0 == null) {
            return;
        }
               
        double sx = encuadreFinal.getWidth() / encuadreInicial.getWidth();
        double sy = encuadreFinal.getHeight() / encuadreInicial.getHeight();

        if (sx == 0) {
            sx = Flotantes.EPSILON;
        }
        
        if (sy == 0) {
            sy = Flotantes.EPSILON;
        }
            
        affineTransform.preConcatenate(AffineTransform.getScaleInstance(sx, sy));

        Point2D p1 = ajustar(new Point2D.Double(encuadreFinal.getX(), encuadreFinal.getY()));
        if (p1 == null) {
            return;
        }
        double dx = p1.getX() - p0.getX();
        double dy = p1.getY() - p0.getY();
        affineTransform.translate(dx, dy);
        
        generarTexto();
    }

    public void rotar(Angulo anguloRotacion, double x, double y) {
        affineTransform.preConcatenate(AffineTransform.getRotateInstance(anguloRotacion.getRadianes(), x, y));
        
        generarTexto();
    }

    public Shape transformar(Shape shape) {
        return affineTransform.createTransformedShape(shape);
    }

    public AffineTransform getValor() {
        return affineTransform;
    }

    public void setValor(AffineTransform affineTransform) {
        this.affineTransform = affineTransform;
    }
    
    public void generarTexto() {
        if (affineTransform.isIdentity()) {
            texto = "";
            return;
        }
        
        double[] flatmatrix = new double[6];
        affineTransform.getMatrix(flatmatrix);
        
        StringBuilder builder = new StringBuilder();        
        builder.append("matrix(");
        for (int i = 0; i < flatmatrix.length; i++) {
            if (i > 0) {
                builder.append(",");
            }
            builder.append(Formato.precision(flatmatrix[i]));            
        }
        builder.append(")");
        texto = builder.toString();
    }
    
    @Override
    public void parsear(String texto) throws Exception {
        this.texto = texto;
        
        TransformListParser p = new TransformListParser();
        AWTTransformProducer tp = new AWTTransformProducer();
        p.setTransformListHandler(tp);
        p.parse(texto);
        AffineTransform at = tp.getAffineTransform();
        
        if (at != null) {
            affineTransform = at;
        } else {
            affineTransform.setToIdentity();
        }
    }
}
